package physicalObjects;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import simulation.Simulation;
import forces.CenterMassForce;
import forces.Force;
import forces.Gravity;
import forces.Viscosity;
import forces.WallRepulsion;


/**
 *
 * @author Robert C. Duvall
 * modified by Michael Elgart and Bruce Fu
 */
public class Mass implements PhysicalObj {
    // state
    private Point2D myCenter;
    private Force myVelocity;
    private ArrayList<Force> appliedForces;
    private Dimension mySize;
    private int myID;
    private double myMass;
    private Force myAcceleration;
    private boolean myFixed = false;
    //The given dimension of the mass.
    private final int myDimension = 16;
    private final int myGravityDir = 90;

    /**
     * @param id The ID number of the masses
     * @param x The starting x coordinate of the mass
     * @param y The starting y coordinate of the mass
     * @param mass The actual units of how "heavy" the object is.
     */
    public Mass (int id, double x, double y, double mass) {
        myAcceleration = new Force(); 
        appliedForces = new ArrayList<Force>();
        myMass = mass;
        myID = id;
        setCenter(x, y);
        setVelocity(0, 0);
        setFixed();
        mySize = new Dimension(myDimension, myDimension);
    }

    /**
     * Paints the mass.
     * @param pen The Graphics 2D pen that draws the object
     */
    public void paint (Graphics2D pen) {
        pen.setColor(Color.BLACK);
        pen.fillOval(getLeft(), getTop(), getSize().width, getSize().height);
    }

    /**
     * Updates the location of the mass based on forces acting on it.
     * @param canvas The simulation where all objects are drawn
     * @param dt The change in time occurring between this update and the last
     */
    public void update (Simulation canvas, double dt) {
        if (myMass > 0) {
            loadAllForces();
            for (Force force : appliedForces) {
                applyForce(force);
            }
            appliedForces.clear();
            
            applyForce(getBounce(canvas.getSize()));
            
            // convert force back into Mover's velocity
            getVelocity().sum(myAcceleration);
            myAcceleration.reset();
            // move mass by velocity
            myCenter.setLocation(myCenter.getX() + myVelocity.getXChange() * dt,
                    myCenter.getY() + myVelocity.getYChange() * dt);
        }
    }
    
    /**
     * Set up and calculate all the forces not applied by the springs and add
     * those forces to the arraylist appliedForces
     */
    public void loadAllForces() {
        Gravity gravityForce = new Gravity();
        gravityForce.setGravity(this);
        
        Viscosity visForce = new Viscosity();
        visForce.setViscorsity(this);
        
        CenterMassForce centerMassForce = new CenterMassForce();
        centerMassForce.setCenterMassForce(this);
        
        WallRepulsion wallRepulsionForce = new WallRepulsion();
        wallRepulsionForce.setWallRepulsion(this);
        
        appliedForces.add(gravityForce);
        appliedForces.add(visForce);
        appliedForces.add(centerMassForce);
        appliedForces.add(wallRepulsionForce);
    }

    /**
     * @param f
     *        force to be applied to this mass
     */
    public void applyForce (Force f) {
        myAcceleration.sum(f);
    }

    /**
     * Tests to see if this mass matches the given id.
     * @param id The ID number you are testing
     * @return Returns true if the ID number matches, false otherwise
     */
    public boolean match (int id) {
        return myID == id;
    }

    // add gravity towards bottom


    // check for move out of bounds
    private Force getBounce (Dimension bounds) {
        Force impulse = new Force();
        if (getLeft() < 0) {
          //0 sets the force in rightward direction
            impulse = new Force(0, 2);
            setCenter(getSize().width / 2 + 10, getCenter().getY());
        }
        else if (getRight() > bounds.width) {
            //180 sets the force in leftward direction
            impulse = new Force(180, 2);
            setCenter(bounds.width - getSize().width / 2 - 10, getCenter().getY());
        }
        if (getTop() < 0) {
            //90 sets the force in the downward direction
            impulse = new Force(90, 2);
            setCenter(getCenter().getX(), getSize().height / 2 + 10);
        }
        else if (getBottom() > bounds.height) {
            //-90 sets the force in the upward direction
            impulse = new Force(-90, 2);
            setCenter(getCenter().getX(), bounds.height - getSize().height / 2 - 10);
        }
        impulse.scale(getVelocity().getRelativeMagnitude(impulse));
        return impulse;
    }

    /**
     * Returns shape's velocity.
     */
    public Force getVelocity () {
        return myVelocity;
    }

    /**
     * Resets shape's velocity.
     * @param direction The direction angle the velocity should be set at
     * @param magnitude The magnitude of the velocity vector
     */
    public void setVelocity (double direction, double magnitude) {
        myVelocity = new Force(direction, magnitude);
    }
    


    /**
     * Returns shape's center.
     */
    public Point2D getCenter () {
        return myCenter;
    }

    /**
     * Resets shape's center.
     * @param x The x coordinate you want to set myCenter at
     * @param y The y coordinate you want to set myCenter at
     */
    public void setCenter (double x, double y) {
        myCenter = new Point2D.Double(x, y);
    }

    /**
     * Returns shape's left-most coordinate.
     */
    public int getLeft () {
        return (int) (getCenter().getX() - getSize().width / 2);
    }

    /**
     * Returns shape's top-most coordinate.
     */
    public int getTop () {
        return (int) (getCenter().getY() - getSize().height / 2);
    }

    /**
     * Returns shape's right-most coordinate.
     */
    public int getRight () {
        return (int) (getCenter().getX() + getSize().width / 2);
    }

    /**
     * Reports shape's bottom-most coordinate.
     *
     * @return bottom-most coordinate
     */
    public int getBottom () {
        return (int) (getCenter().getY() + getSize().height / 2);
    }

    /**
     * Returns shape's size.
     */
    public Dimension getSize () {
        return mySize;
    }
    
    /**
     * 
     * @return The mass value
     */
    public double getMass () {
        return myMass;
    }

    /**
     * Checks to see if the mass is fixed.
     * @return Returns true if fixed, false otherwise
     */
    public boolean isFixed() {
        return myFixed;
    }

    /**
     * Switches whether the mass is fixed or not.
     */
    public void setFixed() {
        if (myMass > 0) {
            myFixed = false;
        }
        else {
            myFixed = true;
        }
    }

    /**
     * returns Mass ID.
     */
    public int getID(){
        return myID;
    }
}